package main.nbc;

import main.utils.UTILS;

import java.util.ArrayList;
import java.util.List;

/**
 * NaiveBayesianClassification（BNC，朴素贝叶斯分类器）
 * @author LiuXingWu
 * @create 2021-01-21 17:29
 */
public class NBC {
    private List<double[]> srcData;    // 原始数据集
    private List<double[]> trainData;    // 训练集
    private List<double[]> BCData;   // 患癌训练数据
    private List<double[]> NotBCData;    // 不患癌训练数据
    private List<double[]> testData;    // 测试集

    // 均值
    private double[] avgOfBC;
    private double[] avgOfNotBC;

    // 方差
    private double[] varianceOfBC = new double[4];
    private double[] varianceOfNotBC = new double[4];

    private int testNum;    // 测试总样本
    private int correctNum;    // 预测正确的数据


    public NBC(String fileName) {
        this.srcData = UTILS.NBCDataProcessing(UTILS.readInfoFromFile(fileName));    // 获取原始数据
        this.dataDivision();
        this.TrainingData();
    }

    /**
     * 数据划分，将原始数据划分为训练节和测试集，
     * 原始数据2/3用于做训练集，3/1用于做测试集
     */
    public void dataDivision() {
        trainData = new ArrayList<double[]>();
        testData = new ArrayList<double[]>();

        int len = srcData.size();    // 数据总量
        int subLen = ((len * 2) / 3);  // 数据划分边界
        // 训练集
        for (int i = 0; i < subLen; i++) {
            double[] temp = srcData.get(i);
            trainData.add(temp);
        }
        // 测试集
        for (int i = subLen; i < len; i++) {
            double[] temp = srcData.get(i);
            testData.add(temp);
        }
        // 初始化测试样本总量和正确预测样本总量
        testNum = testData.size();
        correctNum = 0;
    }


    /**
     * 训练参数
     */
    public void TrainingData() {
        BCData = new ArrayList<double[]>();
        NotBCData = new ArrayList<double[]>();
        // 训练集划分为是否恶性样本
        for (double[] d : trainData) {
            if (d[d.length - 1] == 4) {
                BCData.add(d);
            } else if (d[d.length - 1] == 2){
                NotBCData.add(d);
            } else {}
        }
        // 计算均值和方差
        avgOfBC = UTILS.calAvg(BCData);
        avgOfNotBC = UTILS.calAvg(NotBCData);

        varianceOfBC = UTILS.calVariance(BCData);
        varianceOfNotBC = UTILS.calVariance(NotBCData);
    }

    /**
     * 获取概率值
     * @param input
     * @param averageValue
     * @param variance
     * @return
     */
    public double calProbabilityValue(double input, double averageValue, double variance) {
        double result = 0;
        double stanDeviation = Math.sqrt(variance);    // 标准差
        double tempValue = (input - averageValue) / stanDeviation;    // 变成标准的正态分布的下标值
        result = Math.exp(-(Math.pow(tempValue, 2) / 2)) / Math.sqrt(2 * Math.PI);
        return result;
    }

    /**
     * 测试函数
     * @param inputdata
     */
    public void forcast(double[] inputdata) {
        double[] result = {1, 1};
        // 计算恶性癌的概率
        for (int i = 0; i < inputdata.length; i++) {
            result[0] *= calProbabilityValue(inputdata[i], avgOfBC[i], varianceOfBC[i]);    // 每个属性的概率相乘
        }
        result[0] *= (double)BCData.size() / trainData.size();
        // 计算非恶性的概率
        for (int i = 0; i < inputdata.length; i++) {
            result[1] *=calProbabilityValue(inputdata[i], avgOfNotBC[i], varianceOfNotBC[i]);
            // 每个属性的概率相乘
        }
        result[1] *= (double)NotBCData.size()  / trainData.size();
        // 找到最大值
        int flag = 0;
        for (int i = 0; i < result.length; i++) {
            if (result[flag] < result[i]) {
                flag = i;
            }
        }
        if (flag == 0) {    // 当前划分为恶性
            if (inputdata[inputdata.length - 1] == 4) {
                correctNum++;    // 预测正确
            }
        }else if (flag == 1) {    // 当前划分为非恶性
            if (inputdata[inputdata.length - 1] == 2) {
                correctNum++;    // 预测正确
            }
        }else {}
    }

    public void excute() {
        for (double[] d : testData) {
            forcast(d);
        }
        double result = (double)correctNum / testNum;
        System.out.printf("测试样本总数为：%d, 分类正确样本量为：%d。程序分类正确率为：%.4f", testNum, correctNum, result);
    }

    public static void showData(List<double[]> srcdata) {
        for (double[] d : srcdata) {
            for (int i = 0; i < d.length; i++) {
                System.out.print(d[i] + ", ");
            }
            System.out.println();
        }
    }

    public List<double[]> getSrcData() {
        return srcData;
    }

    public List<double[]> getTrainData() {
        return trainData;
    }

    public List<double[]> getBCData() {
        return BCData;
    }

    public List<double[]> getNotBCData() {
        return NotBCData;
    }

    public List<double[]> getTestData() {
        return testData;
    }

    public double[] getAvgOfBC() {
        return avgOfBC;
    }

    public double[] getAvgOfNotBC() {
        return avgOfNotBC;
    }

    public double[] getVarianceOfBC() {
        return varianceOfBC;
    }

    public double[] getVarianceOfNotBC() {
        return varianceOfNotBC;
    }

    public int getTestNum() {
        return testNum;
    }

    public int getCorrectNum() {
        return correctNum;
    }
}
